// <copyright file="TradeDecider.cs">
// Copyright 2008, Ziv, Moshe and Eli.
// </copyright>

namespace Trader.Trading
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Threading;
    using Analyzing;
    using Analyzing.Interface;
    using BO.Interface;
    using DataServices.Interface;
    using Indicators.Interface;
    using Indicators.TechnicalAnalysis;
    using Interface;

    /// <summary>
    /// This class manages the decision taking during the trading period
    /// </summary>
    public class TradeDecider : ITradeDecider
    {
        private readonly ISignalsCache signalsCache;

        #region Privbate Member's

        private const decimal minActionGain = 0.25M;

        #endregion

        #region Const'

        public TradeDecider(ISignalsCache signalsCache)
        {
            this.signalsCache = signalsCache;
        }

        #endregion

        public IPortfolio Trade(IMoney initialMoney, IList<IStock> stocks, DateTime startTradingDate,
                                DateTime endTradingDate, ICommissionCharger commissionCharger,
                                BackgroundWorker traderWorker, decimal yearlyYeildNoRisk, IRiskChecker riskChecker,
                                IList<ISignal> signals)
        {
            decimal minThroughput = yearlyYeildNoRisk/365/100;

            int daysToCheck = 200;

            traderWorker.ReportProgress(0, "Starting");
            int stepsFinished = 0;
            int timesToCheck = (1 + (int) Math.Floor((endTradingDate - startTradingDate).TotalDays/daysToCheck));
            int totalSteps = (stocks.Count + 1)*timesToCheck;

            IDictionary<IStock, IDictionary<ISignal, IScenarios>> stocksIndicatorsScenarios =
                BuildScenarios(traderWorker, stocks, ref stepsFinished, totalSteps, commissionCharger, startTradingDate, signals);

            if (traderWorker.CancellationPending)
            {
                return null;
            }

            IPortfolio portfolio = new Portfolio(initialMoney, commissionCharger);

            traderWorker.ReportProgress(stepsFinished*100/totalSteps, "Trading");

            for (DateTime currentTradingDay = startTradingDate;
                 currentTradingDay < endTradingDate;
                 currentTradingDay += TimeSpan.FromDays(1))
            {
                if ((currentTradingDay - startTradingDate).TotalDays > 0 &&
                    (currentTradingDay - startTradingDate).TotalDays%daysToCheck == 0)
                {
                    stocksIndicatorsScenarios =
                        BuildScenarios(traderWorker, stocks, ref stepsFinished, totalSteps, commissionCharger,
                                       currentTradingDay,signals);
                    if (traderWorker.CancellationPending)
                    {
                        return null;
                    }
                    traderWorker.ReportProgress(++stepsFinished*100/totalSteps, "Trading");
                    Thread.Sleep(1250);
                }

                portfolio.UpdateUnits(currentTradingDay);

                List<IRecommendation> currentDateRecomendations =
                    GetCurrentDateRecomendations(stocksIndicatorsScenarios, currentTradingDay, minThroughput);

                bool darinedSold = SellDrainedQuotes(currentTradingDay, portfolio, minThroughput);

                if (!darinedSold && !currentDateRecomendations.Any())
                {
                    continue;
                }

                IEnumerable<IRecommendation> portfolioRecomendation = from portfolioMember in portfolio.PortfolioMembers
                                                                      select portfolioMember.Recommendation;

                IEnumerable<IRecommendation> allRecomendations = currentDateRecomendations.Union(portfolioRecomendation);
                List<IRecommendation> filteredRecomendations =
                    allRecomendations.Where(
                        recomendation =>
                        !allRecomendations.Where(
                             innerRecomendation =>
                             recomendation.Stock == innerRecomendation.Stock &&
                             recomendation.GetCurrentChangePerDay(currentTradingDay) <
                             innerRecomendation.GetCurrentChangePerDay(currentTradingDay)).Any()).ToList();

                List<IRecommendation> orderedRecommendations = filteredRecomendations.OrderBy(
                    recommendation => (-1)*recommendation.GetCurrentChangePerDay(currentTradingDay)).ToList();

                int medianIndex = orderedRecommendations.Count*2/3;
                IEnumerable<IRecommendation> topRecomendations =
                    filteredRecomendations.Where(
                        recomendation => orderedRecommendations.IndexOf(recomendation) <= medianIndex);

                portfolio.RearrangeUsingRecomendations(topRecomendations.ToList(), currentTradingDay);
            }

            portfolio.SellAll(endTradingDate);
            traderWorker.ReportProgress(++stepsFinished*100/totalSteps, "Done!");

            return portfolio;
        }

        private IDictionary<IStock, IDictionary<ISignal, IScenarios>> BuildScenarios(BackgroundWorker traderWorker,
                                                                                     IEnumerable<IStock> stocks,
                                                                                     ref int stepsFinished,
                                                                                     int totalSteps,
                                                                                     ICommissionCharger
                                                                                         commissionCharger,
                                                                                     DateTime startTradingDate,
                                                                                     IList<ISignal> signals)
        {
            var stocksIndicatorsScenarios = new Dictionary<IStock, IDictionary<ISignal, IScenarios>>();

            foreach (IStock stock in stocks)
            {
                if (traderWorker.CancellationPending)
                {
                    return null;
                }

                traderWorker.ReportProgress(stepsFinished*100/totalSteps, string.Format("Analyzing {0}", stock));
                var generator = new StockSignalScenariosGenerator(signals, signalsCache,
                                                                  minActionGain + commissionCharger.Commission*2);
                IDictionary<ISignal, IScenarios> indicatorScenarios = generator.GenerateScenraios(stock,
                                                                                                  startTradingDate);
                if (indicatorScenarios.Count > 0)
                {
                    stocksIndicatorsScenarios.Add(stock, indicatorScenarios);
                }

                traderWorker.ReportProgress(++stepsFinished*100/totalSteps, string.Format("Analyzing {0}", stock));
            }

            return stocksIndicatorsScenarios;
        }

        private static bool SellDrainedQuotes(DateTime currentTradingDate, IPortfolio portfolio, decimal minThrouput)
        {
            IEnumerable<IPortfolioMember> membersToSell = from portfolioMember in portfolio.PortfolioMembers
                                                          let gainPerDay =
                                                              portfolioMember.Recommendation.GetCurrentChangePerDay(
                                                              currentTradingDate)
                                                          where
                                                              (!gainPerDay.HasValue || gainPerDay.Value <= minThrouput)
                                                          select portfolioMember;

            portfolio.Sell(membersToSell.ToList(), currentTradingDate);
            return membersToSell.Any();
        }

        private static List<IRecommendation> GetCurrentDateRecomendations(
            IEnumerable<KeyValuePair<IStock, IDictionary<ISignal, IScenarios>>> stockIndicatorsScenarios,
            DateTime currentTradingDay, decimal minThrouput)
        {
            var currentDateRecomendations = new List<IRecommendation>();
            foreach (var indicatorScenarios in stockIndicatorsScenarios)
            {
                IStock stock = indicatorScenarios.Key;
                DateTime proposedBuyPoint = currentTradingDay;
                ICollection<ISignal> indicators = indicatorScenarios.Value.Keys;
                IEnumerable<ISignal> signaledIndicators = from indicator in indicators
                                                          where indicator.GenerateRecommendation
                                                                    (stock, proposedBuyPoint) ==
                                                                Indicators.Interface.Recommendation.BUY
                                                          select indicator;

                foreach (ISignal signaledIndicator in signaledIndicators)
                {
                    IScenarios scenarios = indicatorScenarios.Value[signaledIndicator];
                    if (scenarios.GetValuePerDay(new List<decimal> {0}) >= minThrouput)
                    {
                        var recomendation =
                            new Recommendation(stock, signaledIndicator, scenarios, currentTradingDay);
                        currentDateRecomendations.Add(recomendation);
                    }
                }
            }

            return currentDateRecomendations;
        }
    }
}